[ZJOI2018]-历史
$\mathscr{Part.1}$
不难发现就是最大化 access 更改的边数。考虑不带修怎么做。变换一下问题就是最大化每个点被更改的次数和——每个点都可以独立计算,一个点贡献一次当且仅当相邻两次 access 发源于其不同的子树,并且每个点都可以取到其上限(只要合理安排该点每个子树的 access 顺序就可以了,大概可归纳证明)
点 $x$ 的上限是啥呢?设 $s_x = \sum\limits_{i \in subtree(x)} a_i$, $mx_x = \max\limits_{y \in son(x)}(s_y)$, 那么就是 $\min( s_x - 1, 2 * (s_x - mx_x) )$(讨论一下发现是否存在「一个 $a_i$ 过大、其他子树安排不完」的情况都符合该柿)
考虑修改,那么就要应用链剖分啊分治啊树上科技来维护修改点到根的路径了。接下来的操作神仙极了 qaq(细节好多啊摔
$\mathscr{Part.2}$
我们记录每个点 $x$ 当前的贡献类型:是 ①「$s_x - 1$」,还是 ②「$2 (s_x - mx_x)$ 且 $mx_x$ 来自 $x$ 某个子树」,还是 ③「$2 (s_x - mx_x)$ 且 $mx_x$ 来自 $x$ 自己」。
注意到一次修改只会加,加了可能改变某些点的贡献类型。(改完后的)① 型点增加 $w$,②③ 型点不变。我们要是能快速修改这些贡献类型,就能顺便更新答案。
我们让 ② 型点 $x$ 向 $mx_x$ 对应的儿子连一条边。通过移项,我们发现了一个更优的性质:这样的儿子最多只有一个!这就和重链剖分契合了。Orz
我们把 $x$ 向 $mx_x$ 连的边看作实边,向其他儿子连的边看作虚边,形成的实链只有链底是 ① 或者 ③,其余都是 ②。
然后就是一个类 splay access 的操作,连实边或者断实边。
关于复杂度,$x$ 到根路径上又轻又虚的边数不超过 $log \sum a$,因为显然一次至少翻一倍。单次 access 最多改 $log \sum a$ 条虚边为实边。$O(nlog\sum a)$。
用树剖 + 线段树维护虚边位置每次暴改就是对的,但是找虚边这种事怎么能忘了 LCT 本人呢!毕竟这玩意只是有条件的连实边,魔改一发就好了。
「判断实边的断与连」要用到 $s_x$。设修改点为 $x$。考虑用 lct 维护「 $x$ 到根路径上的」$s$。只改深度比 $x$ 小的所以是个区间加 + 单点查询。为了实现方便,我们利用差分思想,设 $val_x = s_x - s_{ch[x, 1]}$,就可以单点修改,并且查询只要把点转到 splay 的根然后询问右子树的 $val$ 和,就不用什么 LCT 套线段树了!伟 大 胜 利
说了这么多,代码还是挺好写的。
code
1 |
|